//
//  BigNum.cpp
//  BigNum
//
//  Created by Sam Jaffe on 9/18/12.
//  Copyright (c) 2012 Sam Jaffe. All rights reserved.
//

#include <algorithm>
#include "BigNum.h"
#include "BigUInt.h"
#include "BigInt.h"

static const BigUInt ZERO(0);
static const BigUInt TEN(10);
static const BigUInt EIGHT(8);

BigNumber::BigNumber() {
    
}

BigNumber& BigNumber::add(const BigNumber& b) {
    sum(*this, b);
    return *this;
}

BigNumber& BigNumber::sub(const BigNumber& b) {
    diff(*this, b);
    return *this;
}

BigNumber& BigNumber::mult(const BigNumber& b) {
    product(*this, b);
    return *this;
}

BigNumber& BigNumber::div(const BigNumber& b) {
    quotient(*this, b);
    return *this;
}

BigNumber& BigNumber::mod(const BigNumber& b) {
    modulo(*this, b);
    return *this;
}

BigNumber& BigNumber::bflip() {
    bflip(*this);
    return *this;
}

BigNumber& BigNumber::band(const BigNumber& b) {
    band(*this, b);
    return *this;
}

BigNumber& BigNumber::bor(const BigNumber& b) {
    bor(*this, b);
    return *this;
}

BigNumber& BigNumber::bxor(const BigNumber& b) {
    bxor(*this, b);
    return *this;
}

BigNumber& BigNumber::bin(const BigNumber& b) {
    bin(*this, b);
    return *this;
}

BigNumber& BigNumber::bout(const BigNumber& b) {
    bout(*this, b);
    return *this;
}

void BigNumber::sum(const BigNumber& a, const BigNumber& b) {
    bool a_min(a.blocks.size() <= b.blocks.size()), carry(false);
    bool eq = a.blocks.size() == b.blocks.size();
    size_t min(a_min ? a.blocks.size() : b.blocks.size()),
    max(!a_min ? a.blocks.size() : b.blocks.size()), i;
    uint64_t rec;
    for (i = blocks.size(); i < max; i++)
        blocks.push_back(0);
    if (eq)
        blocks.push_back(0);
    for (i = 0; i < min; i++) {
        rec = (uint64_t) a.blocks[i] + (uint64_t) b.blocks[i] + carry;
        carry = rec > 0x00000000FFFFFFFF;
        blocks[i] = (ul) (rec & 0x00000000FFFFFFFF);
    }
}

void BigNumber::diff(const BigNumber& a, const BigNumber& b) {
    bool a_min(a.blocks.size() <= b.blocks.size()), dock(false);
    if (!a_min)
        throw "Negative Number's not Supported";
    size_t i;
    int64_t rec;
    for (i = blocks.size(); i < a.blocks.size(); i++)
        blocks.push_back(0);
    for (i = 0; i < b.blocks.size(); i++) {
        rec = (int64_t) a.blocks[i] - (int64_t) b.blocks[i] - dock;
        dock = rec < 0;
        blocks[i] = (ul) (std::abs(rec) & 0x00000000FFFFFFFF);
    }
    for (; i < a.blocks.size(); i++) {
        blocks[i] = a.blocks[i];
    }
}

void BigNumber::product(const BigNumber& a, const BigNumber& b) {
    
}

void BigNumber::quotient(const BigNumber& a, const BigNumber& b) {
    
}

void BigNumber::modulo(const BigNumber& a, const BigNumber& b) {
    if (a >= b) {
        diff(a, b);
        modulo(*this, b);
    }
}

void BigNumber::bflip(const BigNumber& a) {
    int i;
    for (i = 0; i < std::min(blocks.size(), a.blocks.size()); i++)
        blocks[i] = ~a.blocks[i];
    for (; i < a.blocks.size(); i++)
        blocks.push_back(~a.blocks[i]);
}

void BigNumber::band(const BigNumber& a, const BigNumber& b) {
    int i;
    auto min = std::min(b.blocks.size(), a.blocks.size());
    for (i = 0; i < std::min(blocks.size(), min); i++)
        blocks[i] = a.blocks[i] & b.blocks[i];
    for (; i < min; i++)
        blocks.push_back(a.blocks[i] & b.blocks[i]);
}

void BigNumber::bor(const BigNumber& a, const BigNumber& b) {
    int i;
    auto min = std::min(b.blocks.size(), a.blocks.size());
    auto max = std::max(b.blocks.size(), a.blocks.size());
    bool is_a = a.blocks.size() == max;
    for (i = 0; i < std::min(blocks.size(), min); i++)
        blocks[i] = a.blocks[i] | b.blocks[i];
    for (; i < min; i++)
        blocks.push_back(a.blocks[i] | b.blocks[i]);
    for (; i < max; i++)
        blocks.push_back((is_a ? a : b).blocks[i]);
}

void BigNumber::bxor(const BigNumber& a, const BigNumber& b) {
    int i;
    auto min = std::min(b.blocks.size(), a.blocks.size());
    auto max = std::max(b.blocks.size(), a.blocks.size());
    bool is_a = a.blocks.size() == max;
    for (i = 0; i < std::min(blocks.size(), min); i++)
        blocks[i] = a.blocks[i] ^ b.blocks[i];
    for (; i < min; i++)
        blocks.push_back(a.blocks[i] ^ b.blocks[i]);
    for (; i < max; i++)
        blocks.push_back((is_a ? a : b).blocks[i]);
}

void BigNumber::bin(const BigNumber& a, const BigNumber& b) {
    if (BigUInt(a.blocks.size() << 3) <= b) {
        blocks.clear();
        blocks.push_back(0);
    }
    else {
        unsigned int shift(b % EIGHT);
        unsigned int groups(b / EIGHT);
        int i, grabber(0x0 >> shift);
        ul temp(0);
        while (blocks.size() < a.blocks.size() + groups)
            blocks.push_back(0);
        for (i = 0; i < groups; i++)
            blocks[i] = 0;
        for (i = 0; i < a.blocks.size(); i++) {
            blocks[i+groups] = temp + a.blocks[i] << shift;
            temp = grabber & a.blocks[i];
        }
    }
}

void BigNumber::bout(const BigNumber& a, const BigNumber& b) {
    if (BigUInt(a.blocks.size() << 3) <= b) {
        blocks.clear();
        blocks.push_back(0);
    }
    else {
        int shift(b % EIGHT), invshift = 8-shift, groups(b / EIGHT), grabber(0x0 >> shift);
        long int i;
        ul temp(0);
        for (i = 0; i < groups; i++)
            blocks.pop_back();
        for (i = a.blocks.size()-1; i >= groups; i++) {
            blocks[i-groups] = temp + ((a.blocks[i] >> shift) ^ grabber);
            temp = a.blocks[i] << invshift;
        }
    }
}

BigNumber BigNumber::operator+ (const BigNumber& b) const {
    BigUInt ret;
    ret.sum(*this, b);
    return ret;
}

BigNumber BigNumber::operator- (const BigNumber& b) const {
    BigUInt ret;
    ret.diff(*this, b);
    return ret;
}

BigNumber BigNumber::operator* (const BigNumber &b) const {
    BigUInt ret;
    ret.product(*this, b);
    return ret;
}

BigNumber BigNumber::operator/ (const BigNumber &b) const {
    BigUInt ret;
    ret.quotient(*this, b);
    return ret;
}

BigNumber BigNumber::operator% (const BigNumber &b) const {
    BigUInt ret;
    ret.modulo(*this, b);
    return ret;
}

BigNumber BigNumber::operator~ () const {
    BigUInt ret;
    ret.bflip(*this);
    return ret;
}

BigNumber BigNumber::operator& (const BigNumber& b) const {
    BigUInt ret;
    ret.band(*this, b);
    return ret;
}

BigNumber BigNumber::operator| (const BigNumber& b) const {
    BigUInt ret;
    ret.bor(*this, b);
    return ret;
}

BigNumber BigNumber::operator^ (const BigNumber& b) const {
    BigUInt ret;
    ret.bxor(*this, b);
    return ret;
}

BigNumber BigNumber::operator<< (const BigNumber& b) const {
    BigUInt ret;
    ret.bin(*this, b);
    return ret;
}

BigNumber BigNumber::operator>> (const BigNumber& b) const {
    BigUInt ret;
    ret.bout(*this, b);
    return ret;
}

bool BigNumber::operator==(const BigNumber &b) const {
    int i;
    if (this->blocks.size() == b.blocks.size()) {
        for (i = 0; i < this->blocks.size(); i++)
            if (this->blocks[i] != b.blocks[i])
                return false;
    }
    else
        return false;
    return true;
}

bool BigNumber::operator!=(const BigNumber &b) const {
    int i;
    if (this->blocks.size() == b.blocks.size()) {
        for (i = 0; i < this->blocks.size(); i++)
            if (this->blocks[i] <= b.blocks[i])
                return true;
    }
    else
        return true;
    return false;
}

bool BigNumber::operator>(const BigNumber &b) const {
    int i;
    if (this->blocks.size() == b.blocks.size()) {
        for (i = 0; i < this->blocks.size(); i++)
            if (this->blocks[i] <= b.blocks[i])
                return false;
    }
    else if (this->blocks.size() > b.blocks.size())
        return true;
    else
        return false;
    return true;
}

bool BigNumber::operator<(const BigNumber &b) const {
    int i;
    if (this->blocks.size() == b.blocks.size()) {
        for (i = 0; i < this->blocks.size(); i++)
            if (this->blocks[i] >= b.blocks[i])
                return false;
    }
    else if (this->blocks.size() < b.blocks.size())
        return true;
    else
        return false;
    return true;
}

bool BigNumber::operator<=(const BigNumber &b) const {
    int i;
    if (this->blocks.size() == b.blocks.size()) {
        for (i = 0; i < this->blocks.size(); i++)
            if (this->blocks[i] > b.blocks[i])
                return false;
    }
    else if (this->blocks.size() < b.blocks.size())
        return true;
    else
        return false;
    return true;
}

bool BigNumber::operator>=(const BigNumber &b) const {
    int i;
    if (this->blocks.size() == b.blocks.size()) {
        for (i = 0; i < this->blocks.size(); i++)
            if (this->blocks[i] < b.blocks[i])
                return false;
    }
    else if (this->blocks.size() > b.blocks.size())
        return true;
    else
        return false;
    return true;
}

BigNumber::operator std::string() const {
    return make_string(*this);
}

BigNumber::operator int() const {
    return (int) blocks[0];
}

BigNumber::operator long() const {
    return (long) blocks[0];
}

std::ostream& operator<< (std::ostream& a, BigNumber& b) {
    std::vector<ul> list = b.getBlocks();
    std::vector<ul>::iterator it;
    for (it = list.end(); it > list.begin();)
        a.operator<<(*(--it));
    return a;
}

std::string BigNumber::make_string(BigNumber num) {
    std::string output;
    char c;
    while (num > (BigNumber) ZERO) {
        c = '0' + (int) (num % (BigNumber) TEN);
        num = num / (BigNumber) TEN;
        output = c + output;
    }
    return output;
}
